package net;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import core.Frame;
import db.KnownHosts;


import util.Debug;

public class Connector extends Thread {

	private final int SLEEP_MS = 2000;
	private String ip = "localhost";
	private String hostName;
	private ObjectInputStream ois = null;
	private ObjectOutputStream oos = null;
	private Socket socket = null;

	public int initPort;
	private Frame master;
	private String name;
	private boolean shouldRun = true;
	private KnownHosts hosts;

	/**
	 * Constructor
	 */
	public Connector(String ip, int init_port, String name, Frame master) {
//		String[] hosts = util.HostFile.getHosts();
//		ip = hosts[0]; // FIXME Why does Connector terminate when you this code is run?
		if(ip != null) {
			this.ip = ip;
		}
		this.name = name;
		initPort = init_port;
		this.master = master;
		setHostName(this.ip);
		hosts = new KnownHosts();
		shouldRun(true);
	}
	
	/**
	 * Run method
	 */
	public void run() {
		master.writeToLog(name, "Connector thread started...");
		while(true) {
			if(!shouldRun()) {
				master.writeToLog(name, "Connector thread terminating...");
				disconnect();
				break;
			}
			if(connectionIsAlive()) {
				// Connected, so we try send/receive data
				// TODO: read from input stream
			} else {
				// Disconnected, so we try to connect.
				connect();
				bindIOStreams();
			}

			try {
				if(Debug.on) {
					//master.writeToLog(name"Connector thread sleeping...");
				}
				Thread.sleep(SLEEP_MS);
			} catch (InterruptedException e) {
				if(Debug.on && Debug.verbose) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * Resolve name and address
	 * @param serverIP
	 */
	private void setHostName(String serverIP) {
		InetAddress host = null;
		if(serverIP == "localhost") {
			try {
				host = InetAddress.getLocalHost();
			} catch (UnknownHostException e) {
				if(Debug.on) {
					System.err.println("Unable to connect to localhost.");
				}
				if(Debug.verbose) {
					e.printStackTrace();
				}
			}
		} else {
			try {
				host = InetAddress.getByName(serverIP);
			} catch (UnknownHostException e) {
				if(Debug.on) {
					System.err.println("Unable to resolve name or server "
							+ serverIP);
				}
				if(Debug.verbose) {
					e.printStackTrace();
				}
			}
		}
		if(host != null) {
			hostName = host.getHostName();
		} else if(Debug.on) {
			System.err.println("Host name resolving failed");
		}
	}


	/**
	 * Open a connection to the server socket, or close it if it's already open.
	 * @return a connected socket, or null if connection failed.
	 */
	public Socket connect() {
		connect(initPort);
		bindIOStreams();
		int port = 0;
		try {
			port = ois.readInt();
			master.writeToLog(name, "Assigned port "+port+" by daemon");
		} catch (IOException e) {
			if(Debug.verbose) {
				master.writeToLog(name, "Failed to read from ois");
				e.printStackTrace();
			}
			return null;
		} catch (NullPointerException e) {
			if(Debug.verbose) {
				master.writeToLog(name, "ois status: "+ois);
				if(ois != null) {
					e.printStackTrace();
				}
			}
			return null;
		}
		socket = connect(port);
		master.writeToLog(name, "Socket status: "+socket);
		return socket;
	}
	
	private Socket connect(int port) {
		disconnect(); // Clean up from previous connections
		if(socket == null || !socket.isConnected()) {
			try {
				if(Debug.on) {
					master.writeToLog(name, "Connecting to "+hostName+":"+port+"...");
					
				}
				socket = new Socket(hostName, port);
//				socket.setKeepAlive(true);
				if(Debug.on) {
					master.writeToLog(name, "Connected to " + port + " = " + hosts.hosts.get(port));
					master.updateLastMsg(hosts.hosts.get(port));
				}
			} catch (UnknownHostException e) {
				if(Debug.on) {
					master.writeToLog(name, "Connection to "+hostName+" failed: " +
							"Unknown host");
					System.err.println("connect failed: " + 
							e.getMessage());
				}
				if(Debug.verbose) {
					e.printStackTrace();
				}
				return null;
			} catch (IOException e) {
				if(Debug.on) {
					master.writeToLog(name, "Connection to "+hostName+" failed");
					master.writeToLog(name, "connect: Failed.\n"
							+ e.getMessage());
				}
//				if(Debug.verbose) {
//					e.printStackTrace();
//				}
				return null;
			}
		} else if(Debug.on) {
			master.writeToLog(name, "Already connected!");
		}
		return socket;
	}

	/**
	 * Checks connection status by trying to write to socket
	 * @return true if the connection is alive, else false
	 */
	private boolean connectionIsAlive() {
		if(socket == null || !socket.isConnected()) {
			return false;
		} else if(ois == null || oos == null) {
			return false;
		} else {
			//TODO play ping pong
//			try {
//				byte input = 0, oldinput = 0;
//				master.writeToLog(name"Reading from ois");
//				while((input = ois.readByte()) != -1) {
//					master.writeToLog(name"Read: "+input);
//					oldinput = input; // ping = 0
//				}
//				master.writeToLog(name"Finished reading from ois");
//				return oldinput == 0;
//			} catch (IOException e) {
//				return false;
//			}
			return true;
		}
		
	}

	/**
	 * Assigns class variables oos and ois [ObjectInputStream & 
	 * ObjectInputStream]
	 */
	private void bindIOStreams() {
		if(socket != null && socket.isConnected()) {
			try {
				// Get the output and input streams. 
				// Initialize output stream first or deadlock will occur.
				if(oos == null) {
					oos = new ObjectOutputStream(socket.getOutputStream());
				}
				if(ois == null) {
					ois = new ObjectInputStream(socket.getInputStream());
				}
			} catch (IOException e) {
				if(Debug.on) {
					System.err.println("bindIOStreams:" + e.getMessage());
				}
			}
		}
	}

	/**
	 * Dispose system resources
	 */
	public void disconnect() {
		master.writeToLog(name, "Connector thread disconnecting...");
		try {
			// Close input stream first or deadlock will occur.
			if (ois != null) {
				ois.close();
				ois = null;
			}
			if (oos != null) {
				oos.close();
				oos = null;
			}
			if (socket != null && socket.isConnected()) {
				socket.close();
			}
			socket = null;
		} catch (Exception e) {
			if(Debug.on) {
				System.err.println("disposeSources:" + e.getMessage());
			}
			return;
		}
	}
	
	/**
	 * Set state of this Connector
	 * @param shouldRun true if thread should continue execution, false to 
	 * terminate
	 */
	public synchronized void shouldRun(boolean shouldRun) {
		this.shouldRun = shouldRun;
	}
	
	/**
	 * Get state
	 * @return true if thread should continue running, else false
	 */
	private synchronized boolean shouldRun() {
		return shouldRun;
	}
	
//	/**
//	 * Main method
//	 * @param args ignored
//	 */
//	public static void main(String[] args) {
//		Connector conn = new Connector(666);
//		conn.start();
//	}

}
